package leecode;



import java.util.LinkedList;

/**
 * @Classname
 * @Description TODO
 * @Date 2021/7/20 15:56
 * @Created by Alberthch
 *
 * 题目：完全二叉树的节点
 * 给你一棵 完全二叉树 的根节点 root ，求出该树的节点个数。
 *
 * 主要就是遍历一遍二叉树，先序遍历、中序遍历、后续遍历和层次遍历
 */
class Solution_222 {

    public static class TreeNode {
          int val;
          TreeNode left;
          TreeNode right;
          TreeNode() {}
          TreeNode(int val) { this.val = val; }
          TreeNode(int val, TreeNode left, TreeNode right) {
              this.val = val;
              this.left = left;
              this.right = right;
          }
      }

      // 方法一：普适算法，对于一颗没有限制的二叉树，对于此题给的完全二叉树的特点没有利用起来，进一步考虑如何使用完全二叉树的特点更快解出此题。
    public int countNodes_general(TreeNode root) {
        if (root == null){
            return 0;
        }
        return countNodes(root.left) + countNodes(root.right) + 1;
    }

      // 方法二：普适算法，使用层次遍历
    public static int countNodes(TreeNode root) {
        // 首先判断树是非空的
        if(root == null)
            return 0;
        LinkedList<TreeNode> list = new LinkedList<TreeNode>();
        list.add(root);
        int n = 1;
        TreeNode currentNode = null;

        // 当列表是非空时
        while(!list.isEmpty())
        {
            currentNode = list.poll();
            // 顺序的拿取左右两个树
            if(currentNode.left != null)
            {
                list.add(currentNode.left);
                n++;
            }
            if(currentNode.right != null) {
                list.add(currentNode.right);
                n++;
            }
        }
        return n;
    }

    // 方法三：官方解法，二分查找加上位运算
    public int countNodesLeecode1(TreeNode root) {
        if (root == null) {
            return 0;
        }
        int level = 0;
        TreeNode node = root;

        // 不停往左下进行探索，得到层数，层数从零开始
        while (node.left != null) {
            level++;
            node = node.left;
        }
        int low = 1 << level, high = (1 << (level + 1)) - 1;
        while (low < high) {
            int mid = (high - low + 1) / 2 + low;
            if (exists(root, level, mid)) {
                low = mid;
            } else {
                high = mid - 1;
            }
        }
        return low;
    }

    // 判断第k个节点是否存在
    public boolean exists(TreeNode root, int level, int k) {
        int bits = 1 << (level - 1);
        TreeNode node = root;
        while (node != null && bits > 0) {
            if ((bits & k) == 0) {
                node = node.left;
            } else {
                node = node.right;
            }
            bits >>= 1;
        }
        return node != null;
    }

    // 方法四：
    public int countNodesLeecode2(TreeNode root) {
        if(root == null){
            return 0;
        }

        // 计算左右子树深度
        int left = countLevel(root.left);
        int right = countLevel(root.right);
        if(left == right){
            // left == right。这说明，左子树一定是满二叉树，因为节点已经填充到右子树了，左子树必定已经填满了。
            // 所以左子树的节点总数我们可以直接得到，是 2^left - 1，加上当前这个 root 节点，则正好是 2^left。
            // 再对右子树进行递归统计。
            return countNodes(root.right) + (1<<left);
        }else{
            // left != right。说明此时最后一层不满，但倒数第二层已经满了，右子树是满二叉树，可以直接得到右子树的节点个数。
            // 同理，右子树节点 +root 节点，总数为 2^right。再对左子树进行递归查找。
            return countNodes(root.left) + (1<<right);
        }
    }

    // 给一个树节点，计算它的深度
    private int countLevel(TreeNode root){
        int level = 0;
        while(root != null){
            level++;
            root = root.left;
        }
        return level;
    }



    public static void main(String[] args) {
        TreeNode root = new TreeNode(1);
        root.left = new TreeNode(2);
        root.right = new TreeNode(3);
        System.out.println(countNodes(root));
    }

}
